﻿(****************************************************************************
 * 
 *  Bistro FSharp Extensions Copyright © 2003-2009 Hill30 Inc
 *
 *  This file is part of Bistro Framework.
 *
 *  Bistro FSharp Extensions is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Bistro FSharp Extensions is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Bistro FSharp Extensions.  If not, see <http://www.gnu.org/licenses/>.
 *  
 ***************************************************************************)

module DispatcherTests

    open NUnit.Framework
    open Bistro.Controllers
    open Bistro.Controllers.Dispatch
    open System.Collections
    open Bistro.Controllers.OutputHandling
    open Bistro.Configuration.Logging
    open Bistro.Configuration
    open Bistro

    open Bistro.UnitTests.Support

    open System.Configuration
    open System.Reflection
    open System.Web
    open System.IO

    [<TestFixture>]
    type DispatcherTest() =
        do
            let section = new Bistro.Configuration.SectionHandler()
            
            section.DefaultFormatter <- "Json";
            section.WebFormatters.Add(new NameValueConfigurationElement("Json", "Bistro.Extensions.Format.Json.JsonFormatter, Bistro.Extensions"));
            section.WebFormatters.Add(new NameValueConfigurationElement("Xml", "Bistro.Extensions.Format.Xml.XmlFormatter, Bistro.Extensions"));

            section.ControllerHandlerFactory <- "Bistro.FS.Controller+HandlerFactory,Bistro.FSharpExtensions"
            section.ControllerManagerFactory <- "Bistro.FS.Controller+ManagerFactory,Bistro.FSharpExtensions"
            
            Application.Initialize section

        let application = Application.Instance
        let manager = application.ManagerFactory.GetManagerInstance()
        let dispatcher = application.DispatcherFactory.GetDispatcherInstance()
        let handler = new TestingHandler()
        let controllerCountOffset = 2
        
        let matchController controllerType (controllerInfo: ControllerInvocationInfo) = 
            controllerInfo.BindPoint.Controller.ControllerTypeName = controllerType
        
        let isPresentIn (controllers: ControllerInvocationInfo array) controllerType =
            Array.exists (matchController controllerType) controllers
            
        let isBefore after (controllers: ControllerInvocationInfo array) before =
            match 
                Array.tryFindIndex (matchController before) controllers, 
                Array.tryFindIndex (matchController after) controllers with
            | Some b, Some a -> b <= a
            | _,_ -> false

        [<TestFixtureTearDown>]
        member x.Cleanup() = ()
        
        [<Test>]
        member x.ApplicationCreated() = Assert.IsNotNull(Application.Instance, "Application not created")

        [<Test>]
        member x.DispatcherCreated() = Assert.IsNotNull(dispatcher);

        //[<Test>]
        member x.HomeURL() =
            let controllers = dispatcher.GetControllers "GET/fs/"

            Assert.AreEqual(2 + controllerCountOffset, controllers.Length);

            Assert.That("HomeUrlController1" |> isPresentIn controllers, "HomeUrlController1 is missing");
            Assert.That("HomeUrlController2" |> isPresentIn controllers, "HomeUrlController2 is missing");

        [<Test>]
        member x.HelloYou1() =
            let controllers = dispatcher.GetControllers "GET/fs/hello/world"
            Assert.AreEqual(0 + controllerCountOffset, controllers.Length);

        [<Test>]
        member x.HelloYou2() =
            let controllers = dispatcher.GetControllers "GET/fs/hello/how/are/you"
            Assert.AreEqual(1 + controllerCountOffset, controllers.Length);
            Assert.That("HelloYouController1" |> isPresentIn controllers, "Controller HelloYouController1 is missing");

        [<Test>]
        member x.HelloYou3() =
            let controllers = dispatcher.GetControllers "GET/fs/hello/howAre/you"
            Assert.AreEqual(2 + controllerCountOffset, controllers.Length);
            Assert.That("HelloYouController1" |> isBefore "HelloYouController2" controllers, "HelloYouController1 should be before HelloYouController2");

        //This test checks the correct binding and order of 2 related controllers
        [<Test>]
        member x.URLs2() =
            let controllers = dispatcher.GetControllers "GET/fs/one_little_url"
            Assert.That(controllers.Length = 2 + controllerCountOffset, sprintf "We have %d controllers bound to the URL \"/one_little_url\" instead of 2" (controllers.Length - controllerCountOffset));
            Assert.That("littleController2" |> isBefore "littleController1" controllers, "Wrong order, problems with littleController1");
            Assert.That("littleController1" |> isBefore "Bistro.Controllers.OutputHandling.ReturnTypesController" controllers, "Wrong order, problems with ReturnTypesController");

        //This test checks the correct binding and order of 3 related controllers
        [<Test>]
        member x.URLs3() =
            let controllers = dispatcher.GetControllers "GET/fs/little_url/more"
            Assert.That(controllers.Length = 3 + controllerCountOffset, sprintf "We have %d controllers bound to the URL \"/little_url/more\" instead of 3" (controllers.Length - controllerCountOffset));
            Assert.That("littleController3" |> isBefore "littleController5" controllers, "Wrong order, problems with littleController5");
            Assert.That("littleController5" |> isBefore "littleController4" controllers, "Wrong order, problems with littleController4");
            Assert.That("littleController4" |> isBefore "Bistro.Controllers.OutputHandling.ReturnTypesController" controllers, "Wrong order, problems with ReturnTypesController");

        //This test checks that rather complicated graph of controllers will be invoked in correct order
        //Controllers 1 to 6 should be in the following order: 5,2,1,4,3,6. controller 7 should 
        //be invoked at any position earlier than controller 6. For detailed dependencies see Controllers.cs
        //file, controllers OrderController1....OrderController7
        [<Test>]
        member x.Order() =
            let controllers = dispatcher.GetControllers "GET/fs/order/world/new"
            Assert.That(controllers.Length = 7 + controllerCountOffset, sprintf "We have %d controllers bound to the URL \"/order/world/new\" instead of %d" controllers.Length (7 + controllerCountOffset))
            
            let _, controllerSequence =
                controllers |>
                Array.fold (fun state ctr ->
                                let i, str = state
                                let c = ctr.BindPoint.Controller.ControllerTypeName
                                let num = c.[c.Length - 1..]
                                if (c.EndsWith "ReturnTypesController") || (c.EndsWith "AjaxDeterminer") then
                                    (i, str)
                                elif num = "7" then
                                    Assert.That(i < 6, "Seventh controller will be invoked too late, causing chaos, panic and destruction")
                                    (i + 1, str)
                                else
                                    (i + 1, str + num)
                            ) (0, System.String.Empty)

            Assert.That((controllerSequence = "521436"), sprintf "Controllers will be invoked in wrong order (%s instead of \"521436\"), causing chaos, panic and destruction" controllerSequence)
            
        [<Test>]
        member x.LittleControllerValues() =
            let resp = handler.RunForTest "GET/fs/little_url/more"
            let contexts = handler.AllContents
            Assert.That(contexts.["request"].["l4"] = ("4353" :> obj), sprintf "Resource value %A is unexpected" contexts.["request"].["l4"])
            
        [<Test>]
        member x.Parameters() =
            let resp = handler.RunForTest "GET/fs/parameters/more"
            let contexts = handler.AllContents
            Assert.That(contexts.["request"].["c_parm"] = ("more" :> obj), sprintf "Resource value %A is unexpected" contexts.["request"].["c_parm"])
            
        [<Test>]
        member x.EmptyParameters() =
            let resp = handler.RunForTest "GET/fs/empty_parameters"
            let contexts = handler.AllContents
            Assert.That(contexts.["request"].["c_parm"] = ("" :> obj), sprintf "Resource value %A is unexpected" contexts.["request"].["c_parm"])

        [<Test>]        
        member x.SessionCrossoverParameters() =
            let resp = handler.RunForTest "GET/fs/ssn_parameters/test"
            let contexts = handler.AllContents
            Assert.AreEqual(("test312" :> obj), contexts.["request"].["n_parm"])
            Assert.AreEqual(("test31" :> obj), contexts.["session"].["d_parm"])
            Assert.AreEqual(("test3" :> obj), contexts.["session"].["c_parm"])
            
        [<Test>]        
        member x.FormFieldPassthrough() =
            let formData = new System.Collections.Specialized.NameValueCollection();
            formData.Add("hello", "1");
            formData.Add("world", "oh ");
            let resp = handler.RunForTest("GET/fs/formParmsController1", formData)
            let contexts = handler.AllContents
            Assert.AreEqual("1", contexts.["request"].["hello"])
            Assert.AreEqual("oh hi", contexts.["request"].["world"])
            Assert.AreEqual(1, contexts.["request"].["d"])
            
        [<Test>]        
        member x.FormFieldNoPassthrough() =
            let formData = new System.Collections.Specialized.NameValueCollection();
            formData.Add("hello", "1");
            formData.Add("world", "oh ");
            let resp = handler.RunForTest("GET/fs/formParmsController2", formData)
            let contexts = handler.AllContents
            Assert.That(not <| contexts.["request"].ContainsKey("hello"))
            Assert.AreEqual("oh hi", contexts.["request"].["world"])
            Assert.AreEqual(1, contexts.["request"].["d"])
            
        [<Test>]        
        member x.FormFieldSimple() =
            let formData = new System.Collections.Specialized.NameValueCollection();
            formData.Add("foo", "oh ");
            let resp = handler.RunForTest("GET/fs/formParmsController3", formData)
            let contexts = handler.AllContents
            Assert.That(not <| contexts.["request"].ContainsKey("foo"))
            Assert.AreEqual("oh 1", contexts.["request"].["world"])
            Assert.AreEqual("1", contexts.["request"].["d"])
            
        [<Test>]        
        member x.NullParams() =
            let resp = handler.RunForTest("GET/fs/nullParmsController")
            let contexts = handler.AllContents
            Assert.AreEqual("empty", contexts.["request"].["outVal"])
            
        [<Test>]        
        member x.OptionParams() =
            let resp = handler.RunForTest("GET/fs/nullParmsController/2")
            let contexts = handler.AllContents
            Assert.AreEqual("hellovalue", contexts.["request"].["outVal"])
            
        [<Test>]        
        member x.NoReturnController() =
            let resp = handler.RunForTest("GET/fs/static/notstuff")
            Assert.AreEqual("blah", resp)

        [<Test>]        
        member x.NoCtxNoParamsController() =
            let resp = handler.RunForTest("GET/fs/noctx")
            let contexts = handler.AllContents
            Assert.AreEqual("bah", contexts.["request"].["stuff"])

        [<Test>]        
        member x.NoCtxWithParamController() =
            let resp = handler.RunForTest("GET/fs/noctx2")
            let contexts = handler.AllContents
            Assert.AreEqual("bah", contexts.["request"].["stuff2"])

        [<Test>]        
        member x.SecurityEdgeCaseController() =
            let resp = handler.RunForTest("GET/posting")
            let contexts = handler.AllContents
            Assert.True(contexts.["request"].ContainsKey("currentUser"))
            Assert.IsNull(contexts.["request"].["currentUser"])